﻿using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization;

namespace Microsoft.WowAddonStudio.FrameXml.Design.Serialization
{
	public abstract class FrameXmlSerializer
    {
        private const string wowNamespace = "http://www.blizzard.com/wow/ui/";
        private static readonly XmlSerializer serializer = new XmlSerializer(typeof(Ui));

        /// <summary>
        /// Reads the FrameXML.
        /// </summary>
        /// <returns>A <see cref="String"/> containing the FrameXML.</returns>
        public abstract string ReadFrameXml();

        /// <summary>
        /// Writes the FrameXML.
        /// </summary>
        /// <param name="frameXml">A <see cref="String"/>containing the FrameXML.</param>
        public abstract void WriteFrameXml(string frameXml);

		/// <summary>
		/// Gets or sets the document moniker.
		/// </summary>
		/// <value>The document moniker.</value>
		public string DocumentMoniker { get; set; }

        /// <summary>
        /// Gets or sets the path to the FrameXML schema.
        /// </summary>
        public string SchemaPath { get; set; }

        /// <summary>
        /// Checks whether the FrameXML can be deserialized.
        /// </summary>
        /// <returns>True, if the FrameXML can be deserialized; False otherwise.</returns>
        public bool CanDeserialize()
        {
            string frameXml = this.ReadFrameXml();

            if (frameXml == null)
                throw new InvalidOperationException("Could not read FrameXML.");

            try
            {
                bool validate = !String.IsNullOrEmpty(SchemaPath);
                using (var xmlReader = XmlReader.Create(new StringReader(frameXml), this.CreateXmlReaderSettings(validate)))
                {
                    return serializer.CanDeserialize(xmlReader);
                }
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Deserializes the FrameXML and returns the root <see cref="Ui"/> element.
        /// </summary>
        /// <returns>An instance of the <see cref="Ui"/> class.</returns>
        public Ui Deserialize()
        {
            string frameXml = this.ReadFrameXml();

            bool validate = !String.IsNullOrEmpty(SchemaPath);
            using (var xmlReader = XmlReader.Create(new StringReader(frameXml), this.CreateXmlReaderSettings(validate)))
            {
                // Deserialize the FrameXML to a Ui element
                var ui = (Ui)serializer.Deserialize((xmlReader));

                // Initialize the parents
                ui.InitializeParent();

                // Return the Ui
                return ui;
            }
        }

        /// <summary>
        /// Serializes a FrameXML hierarch and writes the FrameXML.
        /// </summary>
        /// <param name="ui">The root <see cref="Ui"/> element to serialize.</param>
        public void Serialize(Ui ui)
        {
            if (ui == null)
                throw new ArgumentNullException("ui");

            var frameXmlBuilder = new StringBuilder();

			using (XmlWriter writer = XmlWriter.Create(frameXmlBuilder, CreateXmlWriterSettings()))
			{
				serializer.Serialize(writer, ui);
			}

            this.WriteFrameXml(frameXmlBuilder.ToString());
        }

		/// <summary>
		/// Creates the XML writer settings.
		/// </summary>
		/// <returns></returns>
		private static XmlWriterSettings CreateXmlWriterSettings()
		{
			return new XmlWriterSettings
			{
				Indent = true,
				IndentChars = "\t",
				OmitXmlDeclaration = true
			};
		}

    	/// <summary>
        /// Creates <see cref="XmlReaderSettings"/> for reading FrameXML files.
        /// </summary>
        /// <param name="validate">A value indicating whether the <see cref="XmlReader"/> should validate the FrameXML.</param>
        /// <returns>An instance of the <see cref="XmlReaderSettings"/> class.</returns>
        private XmlReaderSettings CreateXmlReaderSettings(bool validate)
        {
            var xmlReaderSettings = new XmlReaderSettings
            {
                IgnoreComments = true,
                ValidationType = validate ? ValidationType.Schema : ValidationType.None,
				IgnoreProcessingInstructions = true
            };

            if (validate)
            {
                using (Stream stream = new FileStream(this.SchemaPath, FileMode.Open, FileAccess.Read))
                using (XmlReader reader = XmlReader.Create(stream))
                {
                    xmlReaderSettings.Schemas.Add(wowNamespace, reader);
                }
            }

            return xmlReaderSettings;
        }
    }
}
